package com.itjeffrey.autocode.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itjeffrey.autocode.bo.req.MenuInfoBO;
import com.itjeffrey.autocode.bo.resp.MenuBO;
import com.itjeffrey.autocode.bo.resp.SubMenuBO;
import com.itjeffrey.autocode.common.PageResult;
import com.itjeffrey.autocode.common.Result;
import com.itjeffrey.autocode.constant.SysConstant;
import com.itjeffrey.autocode.entity.MenuInfoEntity;
import com.itjeffrey.autocode.entity.ResInfoTbEntity;
import com.itjeffrey.autocode.entity.RoleInfoTbEntity;
import com.itjeffrey.autocode.mapper.MenuInfoMapper;
import com.itjeffrey.autocode.service.MenuInfoService;
import com.itjeffrey.autocode.service.ResInfoTbService;
import com.itjeffrey.autocode.service.RoleInfoTbService;
import com.itjeffrey.autocode.util.BeanOprUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @From: Jeffrey
 * @Date: 2021/4/17
 */
@Service
public class MenuInfoServiceImpl extends ServiceImpl<MenuInfoMapper, MenuInfoEntity> implements MenuInfoService {

    private static Logger log = LoggerFactory.getLogger(MenuInfoServiceImpl.class);

    @Resource
    private MenuInfoMapper menuInfoMapper;

    @Resource
    private RoleInfoTbService roleInfoTbService;

    @Resource
    private ResInfoTbService resInfoTbService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result addMenu(MenuInfoBO menuInfoBO) {
        MenuInfoEntity menuInfoEntity = BeanOprUtils.copyProperties(menuInfoBO, MenuInfoEntity::new);
        menuInfoEntity.setCreateTime(LocalDateTime.now());
        menuInfoMapper.insert(menuInfoEntity);
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result modMenu(MenuInfoBO menuInfoBO) {
        MenuInfoEntity menuInfoEntity = BeanOprUtils.copyProperties(menuInfoBO, MenuInfoEntity::new);
        menuInfoMapper.updateById(menuInfoEntity);
        return Result.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result deleteMenu(Integer menuId) {
        menuInfoMapper.deleteById(menuId);
        return Result.ok();
    }

    @Override
    public Result queryList(MenuInfoBO menuInfoBO) {
        PageResult<MenuInfoBO> pageResult = new PageResult<MenuInfoBO>().setCurPageAndSize(menuInfoBO.calCurPage());
        LambdaQueryWrapper<MenuInfoEntity> queryWrapper = new QueryWrapper<MenuInfoEntity>().lambda();
        queryWrapper.like(StringUtils.isNotBlank(menuInfoBO.getAction()), MenuInfoEntity::getAction,
                menuInfoBO.getAction());
        queryWrapper.like(StringUtils.isNotBlank(menuInfoBO.getTitle()), MenuInfoEntity::getTitle,
                menuInfoBO.getTitle());
        queryWrapper.like(StringUtils.isNotBlank(menuInfoBO.getPath()), MenuInfoEntity::getPath, menuInfoBO.getPath());
        queryWrapper.eq(menuInfoBO.getParentId() != null, MenuInfoEntity::getParentId, menuInfoBO.getParentId());
        Integer count = menuInfoMapper.selectCount(queryWrapper);
        queryWrapper.apply(SysConstant.DB_KEYWORD_LIMIT + pageResult.getCurPage() + SysConstant.SPECIAL_CHAR_3 + pageResult.getSize());
        List<MenuInfoEntity> menuInfoEntities = menuInfoMapper.selectList(queryWrapper);
        pageResult.setTotals(count);
        pageResult.setData(BeanOprUtils.copyListProperties(menuInfoEntities, MenuInfoBO::new));
        return Result.ok(pageResult);
    }

    @Override
    public Result querypList(MenuInfoBO menuInfoBO) {
        LambdaQueryWrapper<MenuInfoEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MenuInfoEntity::getParentId, null);
        List<MenuInfoEntity> menuInfoEntities = menuInfoMapper.selectList(wrapper);
        return Result.ok(BeanOprUtils.copyListProperties(menuInfoEntities, MenuInfoBO::new));
    }

    @Override
    public Result querysList(MenuInfoBO menuInfoBO) {
        LambdaQueryWrapper<MenuInfoEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MenuInfoEntity::getParentId, menuInfoBO.getParentId());
        List<MenuInfoEntity> menuInfoEntities = menuInfoMapper.selectList(wrapper);
        return Result.ok(BeanOprUtils.copyListProperties(menuInfoEntities, MenuInfoBO::new));
    }

    @Override
    public Result<List<MenuBO>> getAllMenu() {
        log.info("MenuInfoService getAllMenu Start===datetime:{}", LocalDateTime.now());
        List<MenuBO> menuBOS = new ArrayList<>();
        //查询所有菜单
        List<MenuInfoEntity> menuInfoEntities =
                menuInfoMapper.selectList(new QueryWrapper<MenuInfoEntity>().orderByAsc(SysConstant.MENU_ORDER_COLUMN));
        //遍历获取父菜单，子菜单并返回MenuBO给前端
        menuInfoEntities.forEach(p -> {
            MenuBO menuBO = new MenuBO();
            if (Objects.isNull(p.getParentId())) {
                BeanOprUtils.copyProperties(p, menuBO);
                List<MenuInfoEntity> sMenus =
                        menuInfoEntities.stream().filter(t -> t.getParentId() == p.getMenuId()).collect(Collectors.toList());
                List<SubMenuBO> subMenuBOS = new ArrayList<>();
                sMenus.forEach(s -> {
                    SubMenuBO subMenuBO = new SubMenuBO();
                    subMenuBO.setPath(s.getPath());
                    subMenuBO.setTitle(s.getTitle());
                    subMenuBOS.add(subMenuBO);
                });
                menuBO.setItems(subMenuBOS);
                menuBOS.add(menuBO);
            }
        });
        log.info("MenuInfoService getAllMenu End===datetime:{}", LocalDateTime.now());
        return Result.ok(menuBOS);
    }

    @Override
    public Result<List<MenuBO>> getMenuByLoginName(String loginName) {
        log.info("MenuInfoService getMenuByLoginName Start===loginName:{}", loginName);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<MenuBO> menuBOS = new ArrayList<>();
        //查询所有菜单
        List<MenuInfoEntity> menuInfoEntities =
                menuInfoMapper.selectList(new QueryWrapper<MenuInfoEntity>().orderByAsc(SysConstant.MENU_ORDER_COLUMN));
        //根据登录名获取对应用户角色
        Result<List<RoleInfoTbEntity>> roleResult = roleInfoTbService.queryRoleInfosByLoginName(loginName);
        if (Objects.nonNull(roleResult.getData())) {
            List<RoleInfoTbEntity> roleInfoTbEntities = roleResult.getData();
            //根据角色查询对应菜单资源
            roleInfoTbEntities.forEach(roleInfoTbEntity -> {
                List<ResInfoTbEntity> resInfoTbEntities =
                        resInfoTbService.queryResInfosByRoleId(roleInfoTbEntity.getRoleId(), SysConstant.RES_TYPE_CD);
                //遍历角色对应的菜单资源
                //遍历菜单资源匹配菜单路径并封装MenuBO集合返回
                Set<String> pPathSet = new HashSet<>(); //父菜单path集合，去重
                Map<Integer, Set<String>> sPathMap = new HashMap<>(); //子菜单Map，key为父菜单ID，value为对应子菜单的path集合
                Map<Integer, MenuBO> menuMap = new HashMap<>(); //key为父菜单ID，value为MenuBO
                Map<Integer, List<SubMenuBO>> sMenuMap = new HashMap<>(); //key为父菜单ID，value为所有子菜单集合
                for (ResInfoTbEntity resInfoTbEntity : resInfoTbEntities) {
                    String menuUrl = resInfoTbEntity.getResUrl();
                    //从菜单URL中获取对应的父路径和子路径，封装返回BO
                    String[] strs = menuUrl.split(SysConstant.SPECIAL_CHAR_16);
                    //menuUrl为父菜单路径
                    if (menuUrl.startsWith(SysConstant.SPECIAL_CHAR_16) && strs.length < 3) { // eg: /index

                        //父路径匹配并去重，获取对应父菜单列表
                        Optional<MenuInfoEntity> first = menuInfoEntities.stream()
                                .filter(menuInfoEntity -> menuInfoEntity.getPath().equals(menuUrl)
                                        && Objects.isNull(menuInfoEntity.getParentId())
                                        && !pPathSet.contains(menuUrl) && pPathSet.add(menuUrl)).findFirst();
                        if (first.isPresent()) {
                            MenuInfoEntity pMenu = first.get();
                            if (!menuMap.containsKey(pMenu.getMenuId())) {
                                MenuBO menuBO = new MenuBO();
                                BeanOprUtils.copyProperties(pMenu, menuBO);
                                menuMap.put(pMenu.getMenuId(), menuBO);
                            }
                        }

                    } else if (menuUrl.startsWith(SysConstant.SPECIAL_CHAR_16) && strs.length == 3) { //eg: /index/demo
                        String pPath = SysConstant.SPECIAL_CHAR_16 + strs[1];
                        String sPath = SysConstant.SPECIAL_CHAR_16 + strs[2];

                        //pPathSet包含父路径
                        if (pPathSet.contains(pPath)) {
                            Collection<MenuBO> values = menuMap.values();
                            for (MenuBO value : values) {
                                if (pPath.equals(value.getPath())) {
                                    //根据value找到key
                                    Optional<Integer> first =
                                            menuMap.entrySet().stream().filter(k -> Objects.equals(k.getValue(),
                                            value)).map(Map.Entry::getKey).findFirst();
                                    if (first.isPresent()) {
                                        Integer pMenuId = first.get();
                                        if (sMenuMap.containsKey(pMenuId)) {
                                            List<SubMenuBO> subMenuBOS = sMenuMap.get(pMenuId);
                                            boolean present =
                                                    subMenuBOS.stream().anyMatch(t -> Objects.equals(t.getPath(),
                                                            sPath));
                                            if (!present) {
                                                Optional<MenuInfoEntity> second = menuInfoEntities.stream()
                                                        .filter(menuInfoEntity -> menuInfoEntity.getPath().equals(sPath)
                                                                && Objects.equals(menuInfoEntity.getParentId(),
                                                                pMenuId))
                                                        .findFirst();
                                                SubMenuBO subMenuBO = new SubMenuBO();
                                                if (second.isPresent()) {
                                                    subMenuBO.setTitle(second.get().getTitle());
                                                    subMenuBO.setPath(sPath);
                                                    subMenuBOS.add(subMenuBO);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        //父路径匹配并去重，获取对应父菜单列表
                        Optional<MenuInfoEntity> first = menuInfoEntities.stream()
                                .filter(menuInfoEntity -> menuInfoEntity.getPath().equals(pPath)
                                        && Objects.isNull(menuInfoEntity.getParentId()))
                                .findFirst();
                        if (first.isPresent()) {
                            MenuInfoEntity pMenu = first.get();

                            if (!menuMap.containsKey(pMenu.getMenuId())) {
                                MenuBO menuBO = new MenuBO();
                                BeanOprUtils.copyProperties(pMenu, menuBO);
                                menuMap.put(pMenu.getMenuId(), menuBO);
                            }

                            //查找对应sPath的菜单实体，对subMenuBO赋值，并标记该menuBO为子菜单项待确认状态
                            Optional<MenuInfoEntity> optional1 =
                                    menuInfoEntities.stream().filter(t -> t.getPath().equals(sPath)
                                    && t.getParentId() == pMenu.getMenuId()).findFirst();
                            if (optional1.isPresent()) {
                                MenuInfoEntity sMenu = optional1.get();

                                SubMenuBO subMenuBO = new SubMenuBO();
                                subMenuBO.setPath(sMenu.getPath());
                                subMenuBO.setTitle(sMenu.getTitle());

                                if (sMenuMap.containsKey(pMenu.getMenuId())) {
                                    boolean flag1 = false; //判断sMenuMap中的List<SubMenuBO>是否存在当前subMenuBO，不存在则添加入集合
                                    List<SubMenuBO> subMenuBOS = sMenuMap.get(pMenu.getMenuId());
                                    for (SubMenuBO sm : subMenuBOS) {
                                        if (sMenu.getPath().equals(sm.getPath())) {
                                            flag1 = true;
                                        }
                                    }
                                    if (!flag1) {
                                        subMenuBOS.add(subMenuBO);
                                    }
                                } else {
                                    List<SubMenuBO> subMenuBOS = new ArrayList<>();
                                    subMenuBOS.add(subMenuBO);
                                    sMenuMap.put(pMenu.getMenuId(), subMenuBOS);
                                }

                            }
                        }

                    }
                }

                Set<Integer> pMenuIds = sMenuMap.keySet();

                //根据menuMap的父菜单Id所对应的菜单信息中的MenuOrder重排序
                Set<Integer> menuIds = menuMap.keySet();
                List<MenuInfoEntity> list0 =
                        menuInfoEntities.stream().filter(menuInfoEntity -> menuIds.contains(menuInfoEntity.getMenuId())).collect(Collectors.toList());
                List<MenuInfoEntity> list1 =
                        list0.stream().sorted(Comparator.comparing(MenuInfoEntity::getMenuOrder)).collect(Collectors.toList());
                List<Integer> list2 = list1.stream().map(MenuInfoEntity::getMenuId).collect(Collectors.toList());
                Map<Integer, MenuBO> map1 = new LinkedHashMap<>(); //按插入顺序排序
                list2.forEach(integer -> {
                    if(menuMap.containsKey(integer)){
                        map1.put(integer, menuMap.get(integer));
                    }
                });
                Set<Map.Entry<Integer, MenuBO>> map1s = map1.entrySet();
                for (Map.Entry<Integer, MenuBO> menuMapEntry : map1s) {
                    Integer key = menuMapEntry.getKey();
                    MenuBO value = menuMapEntry.getValue();
                    if (pMenuIds.contains(key)) {
                        List<SubMenuBO> subMenuBOS = sMenuMap.get(key);
                        List<String> lists0 = subMenuBOS.stream().map(SubMenuBO::getPath).collect(Collectors.toList());

                        //对SubMenuBO中所对应菜单信息的MenuOrder进行重排序
                        List<MenuInfoEntity> lists1 =
                                menuInfoEntities.stream().filter(menuInfoEntity -> lists0.contains(menuInfoEntity.getPath())
                                && menuInfoEntity.getParentId() == key).collect(Collectors.toList());
                        List<MenuInfoEntity> lists2 =
                                lists1.stream().sorted(Comparator.comparing(MenuInfoEntity::getMenuOrder)).collect(Collectors.toList());
                        List<SubMenuBO> lists3 = new ArrayList<>(); //有序，按添加顺序排序
                        lists2.forEach(menuInfoEntity -> {
                            Optional<SubMenuBO> first =
                                    subMenuBOS.stream().filter(subMenuBO -> subMenuBO.getPath().equals(menuInfoEntity.getPath())
                                        && subMenuBO.getTitle().equals(menuInfoEntity.getTitle())).findFirst();
                            if(first.isPresent()){
                                SubMenuBO subMenuBO = first.get();
                                lists3.add(subMenuBO);
                            }
                        });

                        value.setItems(lists3);
                        menuBOS.add(value);
                    }
                }
            });
        }
        stopWatch.stop();
        log.info("MenuInfoService getMenuByLoginName End===loginName:{}===times:{}ms", loginName,
                stopWatch.getTotalTimeMillis());
        return Result.ok(menuBOS);
    }
}
